@using System.Text.RegularExpressions;
@using System.Web.WebPages.Scope;

@functions {  
    
    private const string EmptyKeyValue = "[This section has no content]";
    private const string DefaultHtmlEditorFolder = "QuickEdit/jHtmlAreaFiles";
    private const string DefaultSavePage = "~/QuickEdit/SaveContent";
    
    private static readonly object _administratorRoleName = new object();
    private static readonly object _connectionString = new object();    
    
    private static string AdministratorRoleName {
        get {
            return (string) (ScopeStorage.CurrentScope[_administratorRoleName] ?? "");
        }
        
        set {
            ScopeStorage.CurrentScope[_administratorRoleName] = value;
        }
    }

    private static string ConnectionString {
        get {
            return (string) (ScopeStorage.CurrentScope[_connectionString] ?? "");
        }
        
        set {
            ScopeStorage.CurrentScope[_connectionString] = value;
        }
    }    

    /// <summary>
    /// Initializes the QuickEdit helper, using the Database provided in the connection string 
    /// </summary>
    /// <param name="connectionString">Database where to store the Content table with editable content.</param>
    /// <param name="adminRoleName">Role name for administrator users.</param>
    public static void Initialize(string connectionString, string adminRoleName = "admin") {
        ConnectionString = connectionString;
        AdministratorRoleName = adminRoleName;

        InitializeDB();
    }
    
    /// <summary>
    /// Stores content data sent from a form POST
    /// </summary>
    public static void SaveContentFromPost() {
        // Get current context       
        var context = HttpContext.Current;
        
        // For CSRF protection check the request is POST
        if (context.Request.RequestType != "POST") {
            return;
        }

        // AntiForgery validation
        AntiForgery.Validate();

        // Gather content data from the form POST
        var contentKey = context.Request.Form.Keys[0];
        var contentValue = Sanitize(HttpUtility.HtmlDecode(context.Request.Form[0]));
        
        // Store content data
        SaveContent(contentKey, contentValue);
    }
        
    /// <summary>
    /// Retrieves the content data for a specific content key
    /// </summary>
    /// <param name="contentKey">The key of the content to search for.</param>
    public static string GetContent(string contentKey) {
        var db = Database.Open(ConnectionString);
        var contentValue = db.QueryValue("SELECT [Value] FROM webpages_QuickEditContent WHERE [Key] = @0", contentKey);

        if (contentValue == null) {
            contentValue = HttpUtility.HtmlEncode(EmptyKeyValue);
            db.Execute("INSERT webpages_QuickEditContent([Key], [Value]) VALUES(@0, @1)", contentKey, contentValue);
        }
        
        return contentValue;
    }
    
    /// <summary>
    /// Stores content data into the content storage
    /// </summary>
    /// <param name="contentKey">The content key.</param>
    /// <param name="contentValue">The content data.</param>
    public static void SaveContent(string contentKey, string contentValue) {
        var db = Database.Open(ConnectionString);
        db.Execute("UPDATE webpages_QuickEditContent SET [Value] = @0 WHERE [Key] = @1", contentValue, contentKey);
    }
    
    private static void InitializeDB() {               
        var db = Database.Open(ConnectionString);
        var table = db.QuerySingle("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'webpages_QuickEditContent'");
        
        if (table == null) {
            db.Execute("CREATE TABLE webpages_QuickEditContent(Id bigint IDENTITY(1,1) NOT NULL, [Key] nvarchar(100) NOT NULL, Value ntext NOT NULL DEFAULT(''))");
        }
    }   
       
    private static string Sanitize(string html) {
		var localBaseUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
        var tags = new Regex("<[^>]*(>|$)", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled);       
        var whitelist = new Regex(
                    @"
                    ^</?(b(lockquote)?|code|d(d|t|l|el)|em|h(1|2|3|4|5|6)|i|kbd|li|ol|p(re)?|s(ub|up|trong|trike)?|ul)>$|
                    ^<(b|h)r\s?/?>$",
                    RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
        var whitelistAnchor = new Regex(
                    @"^<a" +
                    @"(\sclass=("")?[^""<>]*("")?)?" +
                    @"\shref=""(\#|/|(\#\d+|(https?|ftp)://|" + localBaseUrl + @")[-a-z0-9+&@#/%?=~_|!:,.;\(\)]*)""" +
                    @"(\stitle=""[^""<>]+"")?\s?>$|" +
                    @"^</a>$",
                    RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
        var whitelistSpan = new Regex(
                    @"
                    ^<span>$|
					^<span
                    (\sclass=("")?[^""<>]*("")?)?
					(\sstyle=""[^""<>]+"")?\s?>$|
                    ^</span>$",
                    RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
        var whitelistDiv = new Regex(
                    @"
                    ^<div>$|
					^<div
                    (\sclass=("")?[^""<>]*("")?)?
					(\sstyle=""[^""<>]+"")?\s?>$|
                    ^</div>$",
                    RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);   
        var whitelistImg = new Regex(
                    @"^<img" +
                    @"(\sclass=("")?[^""<>]*("")?)?" +
                    @"(\salt=""[^""<>]*"")?" +
                    @"(\sheight=""\d{1,3}"")?" +
                    @"\ssrc=""(/|https?://|" + localBaseUrl + @")[-a-z0-9+&@#/%?=~_|!:,.;\(\)]+""" +
                    @"(\stitle=""[^""<>]*"")?" +
                    @"(\swidth=""\d{1,3}"")?" +
                    @"\s?/?>$",
                    RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);

        if (html.IsEmpty()) {
            return html;
        }
           
        var matchTags = tags.Matches(html);
        for (var i = matchTags.Count - 1; i > -1; i--) {
            var tag = matchTags[i];
            var tagname = tag.Value.ToLowerInvariant();

            if (!(whitelist.IsMatch(tagname) || whitelistAnchor.IsMatch(tagname) || whitelistImg.IsMatch(tagname) || whitelistSpan.IsMatch(tagname) || whitelistDiv.IsMatch(tagname))) {
                html = html.Remove(tag.Index, tag.Length);                    
            }
        }

        return html;
    }
}

@*
Summary:
    Outputs the necessary HTML headers for using the QuickEdit helper

Parameter: htmlEditorFolder
    folder containing the jHtmlArea files (http://jhtmlarea.codeplex.com/)

Returns:
    HTML Headers to render on your site
*@
@helper GetHeaderHtml(string htmlEditorFolder = DefaultHtmlEditorFolder) {
    <text>
        <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
        <META HTTP-EQUIV="Expires" CONTENT="-1">
    </text>
    
    <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js" type="text/javascript"></script> 
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery.ui/1.8.5/jquery-ui.min.js"></script>
    <link rel="Stylesheet" type="text/css" href="http://ajax.microsoft.com/ajax/jquery.ui/1.8.5/themes/ui-lightness/jquery-ui.css" />   
    <script type="text/javascript" src="@HelperPage.Href("~/" + htmlEditorFolder + "/scripts/jHtmlArea-0.7.0.js")"></script>
    <link rel="Stylesheet" type="text/css" href="@HelperPage.Href("~/" + htmlEditorFolder + "/style/jHtmlArea.css")" />              
    
    <style type="text/css">
        div.jHtmlArea .ToolBar ul li a.custom_disk_button
        {
        background: url(@HelperPage.Href("~/" + htmlEditorFolder + "/images/disk.png")) no-repeat;
        background-position: 0 0;
        }
        div.jHtmlArea { border: solid 1px #ccc; }
    </style>
}

@*
Summary:
    Displays editable content on the page

Parameter: contentKey
    Key value for the content to display

Parameter: savePage
    Url of the WebMatrix page that handles the saving of the edited content

Returns:
    HTML with Edit button (displayed only when user is from 'Admin' role)
*@
@helper GetContentHtml(string contentKey, string savePage = DefaultSavePage) {
    var contentValue = GetContent(contentKey);
        
    <span id="@contentKey@{<text>_display</text>}">
        @(new HtmlString(contentValue))
    </span>

    if (Roles.IsUserInRole(WebSecurity.CurrentUserName, AdministratorRoleName)) {
        <div id="@contentKey@{<text>_container</text>}">
            <form id="@contentKey@{<text>_form</text>}">
                <input id="@contentKey@{<text>_dialogShow</text>}" type="button" value="Edit" />
                <script type="text/javascript">
                    function escapeHTMLEncode(str) {
                        var div = document.createElement('div')
                        var text = document.createTextNode(str);
                        div.appendChild(text);
                        return div.innerHTML;
                    };
                
                    function postContentData(key, value) {
                        var antiforgeryInput = $('#@contentKey@{<text>_antiforgery</text>} input')[0];
                        return key + '=' + escape(value) + '&' + antiforgeryInput.name + '=' + escape(antiforgeryInput.value);
                    }
                
                    $(function () {
                        var $@contentKey@{<text>_dialog</text>} = $('#@contentKey@{<text>_dialogContainer</text>}').dialog({ width: 440, autoOpen: false });
                        $('#@contentKey@{<text>_dialogShow</text>}').click(function () {
                                                        $@contentKey@{<text>_dialog</text>}.dialog('open');
                                                        $('#@contentKey@{<text>_htmlArea</text>}').htmlarea({
                                                                toolbar: [
                                                                ["html"], ["bold", "italic", "underline"],
                                                                ["p", "h1", "h2", "h3", "h4", "h5", "h6"],
                                                                ["link", "unlink"],
                                                                [{ css: "custom_disk_button", text: "Save", action: function (btn) {
                                                                    $('#@contentKey@{<text>_display</text>}').html(this.toHtmlString());
                                                                    $('#@contentKey').val(escapeHTMLEncode(this.toHtmlString()));
                                                                    var postData = $('#@contentKey@{<text>_form</text>}').serialize();
                                                                    $.post("@HelperPage.Href(DefaultSavePage)", postData, null, 'json');
                                                                    $@contentKey@{<text>_dialog</text>}.dialog('close');
                                                                }}]]
                                                        });
                                                    });
                    });
                
                </script>
                
                <input id="@contentKey" name="@contentKey" type="hidden" />
                @AntiForgery.GetHtml()            
                
                <div id="@contentKey@{<text>_dialogContainer</text>}" style="display: none" title="Edit &quot;@contentKey&quot;">
                    <textarea id="@contentKey@{<text>_htmlArea</text>}" rows="10" style="width: 400px">@contentValue</textarea>
                </div>
            </form>
        </div>
    }
}